package com.example.design_pattern.singleton;

// 线程安全、效率高
// 此种实现中不用每次需要获得锁，减少了获取锁和等待的事件。
// 注意volatile关键字的使用，保证了各线程对singleton静态实例域修改的可见性。

// 上一个懒汉模式直接在getSingleton方法上加锁，这样不管怎么样，只要调用到这个方法的时候，都设计到同步方法，java应该需要时间准备

// 双重校验加锁实现单例的方式为什么效率更高
// 面这个方法是在上面方法的基础上改进的，其效率更高的原因如下：
//1. **减少了不必要的同步**：在getInstance()方法的外部添加了一层null判断，如果实例已经被创建，则无需再进入同步块，直接返回已经创建的实例，避免了多个线程进入同步块竞争锁的开销。
//2. **提高了并发性能**：双重校验锁在实例未创建时才会执行同步块中的代码，因此只有第一次调用getInstance()方法时才会真正同步，后续调用时不会进入同步块，减少了线程阻塞的时间，提高了并发性能。
//3. **保证了线程安全性**：双重校验锁利用了同步块的特性，在多线程环境下能够确保只有一个线程进入同步块创建实例，避免了多线程环境下创建多个实例的问题。
//4. **为什么要有两个if判断呢。第一个if判断，是想让如果有了实例，就不要走同步块了。如果去掉，那么，还是每次调用getSingleton方法，都会直接进入同步块。
// 第二个if判断为什么要加呢，想明白一个东西为什么要加，最好的方法是把这个东西去掉，看会有什么问题。去掉之后，很容易知道，比如A线程首次通过了外面那个instance == null判断，然后还没来得及创建实例的时候，B线程也走到instance == null判断，也通过了判断，那么，是不是可能到时候就会创建不止一个实例了，就不是单例了。
// 所以，严格兰说，synchronized块并没有框柱整个所有instance被使用到的地方(第一个if就没框柱),但是，它根据具体问题具体分析，又在内层多加了个判断，从而规避了可能的不良后果。
// 因此，这种写法是具体问题具体分析的产物、是具体而灵活的，不是一种通用的写代码的惯例。一般性的这种场景下的我们的普遍性的思维是，把公共资源instance所有被使用到的地方，都包住。
//总的来说，双重校验加锁实现单例的方式在保证了线程安全性的同时，通过减少同步块的使用，提高了并发性能，因此效率更高。

// 为什么这个例子中要加volatile。
// 上一个例子中不加，是因为同步机制把整个方法都框起来了。人家是完全串行化的，完全串行化那基本就是解决线程安全最简单粗暴、最严格的方式，那就没什么好说的了。
// 而本例子中，同步机制没有框柱整个设计instance使用的地方，那就算不上完全串行化。
// 不是完全串行化，那么就存在，一个大的操作包含的多个小的操作，被随机乱序执行的问题。那就可能会线程安全的问题。
// 这种举具体例子来说比较好说。比如本例中，保证线程安全的核心设计是添加了第二个if。假设两个线程同时都通过了第一个if(完全有可能)，第二个if拦截起作用有个前提，就是，先来的线程，修改了公共变量的值，其他的线程要可见，才会通不过第二个if。
public class Singleton05LazyWithLockAnDoubleCheck {

    //volatile [ˈvɑːlətl] 不稳定，易变的
    private volatile static Singleton05LazyWithLockAnDoubleCheck instance;

    private Singleton05LazyWithLockAnDoubleCheck() {}

    public static Singleton05LazyWithLockAnDoubleCheck getSingleton() {
        if (instance == null) { // 判断1
            synchronized (Singleton05LazyWithLockAnDoubleCheck.class) {
                if (instance == null) { //判断2
                    instance = new Singleton05LazyWithLockAnDoubleCheck();
                }
            }
        }
        return instance;
    }

}
